package service.arbitrator;

import iface.ArbitratorInterface;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.util.Map;
import java.util.HashMap;

import common.AnonMath;
import common.Point;
import common.SLA;
import common.Settings;
import common.SignatureVerifier;
import common.SignedData;
import common.UserRPCClient;

public class Arbitrator implements ArbitratorInterface{
	int ID;
	Map<Integer,ArbitratorUserDetails> users;

	// signatures
	SignatureVerifier CASignatureVerifier;
	
	//services
	UserRPCClient bulletinBoardWebService;
	public Arbitrator(int id){
		this.ID = id;
		users = new HashMap<Integer,ArbitratorUserDetails>();
		this.bulletinBoardWebService = new UserRPCClient(Settings.BBOARD_URI);
	}	

	/**
	 * Adds a new user to users and creates a new KeyPair 
	 * If the user already exists, return it's private key
	 * @param tmpUserID- user's temporary ID as received from server
	 * @return byte[] representation of the private key 
	 */

	// should add functionality: if user exists, return the private key
	// otherwise, generate and return private key
	private void addUser(int tmpUserID) {
		if (!this.users.containsKey(tmpUserID)){		
			KeyPair userKeyPair = generateKeyPair();
			ArbitratorUserDetails user = new ArbitratorUserDetails(userKeyPair);
			this.users.put(tmpUserID, user);
		}
	}

	/**
	 * Generates a new KeyPair (a pair of private and public keys)
	 * @return
	 */
	private KeyPair generateKeyPair(){
		KeyPair pair = null;
		try{
			KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA", "SUN");
			SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
			keyGen.initialize(1024, random);
			pair = keyGen.generateKeyPair();
		} catch (Exception e) {
			System.err.println("Caught exception " + e.toString());
			e.printStackTrace();
		}
		return pair;
	}


	/**
	 * Verifying matching between data and it's signature for a specific user
	 * @param data- the signed data
	 * @param signature- the signature
	 * @param tmpUserID- temporary user ID as received from the server
	 * @return true iff signature matches data
	 * @throws SignatureException 
	 */
	private boolean verifyMessage(int tmpUserID, byte[] data, byte[] signature) throws SignatureException{
		SignatureVerifier sv = new SignatureVerifier(this.users.get(tmpUserID).keys.getPublic().getEncoded());
		return sv.verify(data, signature);
	}

	public void setCASignatureVerifier(byte[] caPublicKey){
		this.CASignatureVerifier = new SignatureVerifier(caPublicKey);
	}

	private boolean verifyUser(Integer userID, byte[] iDSignature) {
		try {
			return CASignatureVerifier.verify(AnonMath.intToByteArray(userID), iDSignature);
		} catch (SignatureException e) {
			System.out.println("USER: "+e.getMessage());			
			e.printStackTrace();
			return false;
		}
	}
/*
	private byte[] getUserPublicKey(int userID){
		ArbitratorUserDetails user = this.users.get(userID);
		if (user == null){
			System.out.println("Arbitrator: invalid user ID");
			return (byte[])null;
		}
		KeyPair userKey = user.keys;
		return userKey.getPublic().getEncoded();
	}
	*/

	@Override
	public Boolean arbitrate(Integer tmpUserID, String message, byte[] signature) {
		SignatureVerifier sv = new SignatureVerifier(this.users.get(tmpUserID).keys.getPublic().getEncoded());
		try {
			if (!sv.verify(message.getBytes(),signature)){
				return true;
			}
		} catch (SignatureException e) {
			System.out.println(e.getMessage());			
			e.printStackTrace();
			return true;
		}
		boolean ans =SLA.checkMessageSLA(message); 
		if (!ans){
			Class[] returnTypes = new Class[]{boolean.class};
			String namespaceURI = Settings.BBOARD_NS;
			String methodName = "submitClientShare";
			Point share = this.users.get(tmpUserID).share;
			Object[] mArgs = new Object[]{tmpUserID,share};
			this.bulletinBoardWebService.wsCall(returnTypes, namespaceURI, methodName, mArgs);
		}
		return ans;
	}

	@Override
	public byte[] initServerSession(int userID) {
		addUser(userID);
		return this.users.get(userID).keys.getPublic().getEncoded();
	}

	@Override
	public byte[] initUserSession(Integer tmpUserID, byte[] IDSignature,
			String[] commitmentsAsString, SignedData[] commitmentsSignatures, Point share) {
		if (!verifyUser(tmpUserID,IDSignature)){
			return (byte[])null;
		}
		addUser(tmpUserID);
		BigInteger[] commitmentsAsBigInteger = AnonMath.convertStringToBigInteger(commitmentsAsString);
		byte[][] sigcomms = AnonMath.getTableOfSignedCommitments(commitmentsSignatures);
		if (!AnonMath.verifyCommitments(commitmentsAsBigInteger, sigcomms, this.CASignatureVerifier)){
			return (byte[])null;
		}
		if (!AnonMath.verifyShare(share, commitmentsAsBigInteger)){
			return (byte[])null;
		}
		this.users.get(tmpUserID).share = share;
		return this.users.get(tmpUserID).keys.getPrivate().getEncoded();
	}
}
